#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;

typedef struct { float r, g, b, a; } float4;

int main(int argc, char ** argv)
{
    // Initialise GLUT and GLEW.
    //==========================================================================
    glutInit(&argc, argv);
    glutCreateWindow("");
    glewInit();

    // Create a frame buffer object.
    //==========================================================================
    GLsizei nFBO = 2;
    GLuint fboIds[2];
    glGenFramebuffersEXT(nFBO, fboIds); // (n buffers, pointers to buffers)

    // Once the FBO is created it has to be bound before using it.
    //==========================================================================
    // Once bound, all OpenGL operations affecct the current framebuffer object.
    // The object id 0 is reserved for the default window-system framebuffer.
    // Therefore to unbind the current framebuffer, use ID 0 in the 
    // glBindFramebufferEXT() and this will return the framebuffer to the default.
    GLuint fboId = fboIds[0];
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);


    // Create a and setup texture for offscreen rendering.
    //==========================================================================
    GLuint d_texture;
    int size = 8;
    // CPU
    vector<float4> h_dataIn(size * size);
    for (int i = 0; i < size * size; ++i)
    {
        h_dataIn[i].r = (float)i + 0.0;
        h_dataIn[i].g = (float)i + 0.1;
        h_dataIn[i].b = (float)i + 0.2;
        h_dataIn[i].a = (float)i + 0.3;
    }
    // GPU
    glEnable(GL_TEXTURE_2D);
    const GLenum target = GL_TEXTURE_RECTANGLE_ARB;
    const GLenum internalFormat = GL_FLOAT_RGBA32_NV;
    const GLenum format = GL_RGBA;
    const GLenum type = GL_FLOAT;
    glGenTextures(1, &d_texture);
    glBindTexture(target, d_texture); // Bind a named texture to a texture target.
    // Set texture parameters.
    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP);
    // Specify a two-dimensional texture image
    glTexImage2D(target, 0, internalFormat, size, size, 0, format, type,
        &h_dataIn[0]);

    // Attaching images to the FBO. % http://www.songho.ca/opengl/gl_fbo.html
    //==========================================================================
    // glFramebufferTexture2DEXT() is to attach a 2D texture image to a FBO.
    // The first parameter must be GL_FRAMEBUFFER_EXT and the second parameter
    // is the attachment point to where to connect the texture time.
    // A FBO has multple attachments, GL_COLOR_ATTACHMENT0_EXT .. etc.
    // The third paramter is the texture target, GL_TEXTURE_2D for example.
    // The forth parameter is the identifier of the texture object.
    // Last parameter is the mipmap level.
    const GLenum attachment = GL_COLOR_ATTACHMENT0_EXT;
    const GLenum textureTarget = GL_TEXTURE_RECTANGLE_ARB;
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, textureTarget,
        d_texture, 0);


    // Read back texture... to see if we can!
    vector<float4> h_dataOut(size * size);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboIds[0]);
    glReadBuffer(attachment);
    glReadPixels(0, 0, size, size, GL_RGBA, GL_FLOAT, (float*)&h_dataOut[0]);

    // print out values in the texture read back to check its ok.
    for (int i = 0; i < size * size; ++i)
    {
        cout << fixed << setprecision(2);
        cout << left << setw(7) << h_dataOut[i].r 
             << "(" << h_dataIn[i].r << ")" << endl;
        cout << left << setw(7) << h_dataOut[i].g 
             << "(" << h_dataIn[i].g << ")" << endl;
        cout << left << setw(7) << h_dataOut[i].b 
             << "(" << h_dataIn[i].b << ")" << endl;
        cout << left << setw(7) << h_dataOut[i].a 
             << "(" << h_dataIn[i].a << ")" << endl;

    }



    // Delete frame buffer object.
    glDeleteFramebuffersEXT(nFBO, fboIds);

    return 0;
}
